package com.kwin.idempotent.aspect;

import com.kwin.idempotent.annotation.Idempotent;
import com.kwin.idempotent.exception.IdempotentException;
import com.kwin.idempotent.expression.KeyResolver;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author kwin
 * @Date 2022/3/24 15:17
 **/
@Aspect
public class IdempotentAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(IdempotentAspect.class);

    private static final String REPEAT_LOCK_PREFIX = "idempotent_";

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private KeyResolver keyResolver;

    @Pointcut("@annotation( com.kwin.idempotent.annotation.Idempotent)")
    public void pointCut() {
    }
    /**
     * around
     */
    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();
        if (!method.isAnnotationPresent(Idempotent.class)) {
            return joinPoint.proceed(args);
        }
        Idempotent idempotent = method.getAnnotation(Idempotent.class);

        String suffix = "";
        if((idempotent.keys() == null || idempotent.keys().length == 0)
           && (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0)) {
            suffix = Arrays.asList(joinPoint.getArgs()).toString();
        } else {
            suffix = keyResolver.resolver(idempotent, joinPoint);
        }

        String location = null;
        if(!StringUtils.hasText(idempotent.location())) {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();
            location = request.getRequestURI();

        } else {
            location =  idempotent.location();
        }
        String key = REPEAT_LOCK_PREFIX + location  + ":" + suffix;

        return doHandle(joinPoint, args, key, idempotent);
    }

    private Object doHandle(ProceedingJoinPoint joinPoint, Object[] args, String lockKey, Idempotent idempotent)
        throws Throwable {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock == null || !lock.tryLock(0, idempotent.timeout(), idempotent.timeUnit())) {
            LOGGER.error("handle present repeat submission tryLock failed, lockKey: {}", lockKey);
            throw new IdempotentException(idempotent.info());
        }

        try {
            return joinPoint.proceed(args);
        } finally {
            if(idempotent.delKey()) {
                lock.unlock();
            }
        }
    }
}
